﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using Brjnk.Common;
using Brjnk.CoordinateSystem2D.Layers;
using Brjnk.MVVM;

namespace Brjnk.CoordinateSystem2D
{
    /// <summary>
    /// Představuje dvourozměrný systém souřadenic, ve kterém mohou být zobrazovány vrstvy (ILayer)
    /// </summary>
    [TemplatePart(Name = "PART_MouseObserver")]
    public class Area2D : Control
    {
        static Area2D()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Area2D), new FrameworkPropertyMetadata(typeof(Area2D)));
        }

        private SetPropertyHandler viewUpdater;
        private FrameworkElement mouseObserverElement;
        private MouseObserver mouseObserver;

        private Popup toolTip;
        private Popup contextMenu;

        public Area2D()
        {
            View = new ViewInfo(out viewUpdater);
        }

        protected override Size ArrangeOverride(Size arrangeBounds)
        {
            if (IsLoaded == false) PrepareControl();
            return base.ArrangeOverride(arrangeBounds);
        }

        private bool prepared = false;

        private void PrepareControl()
        {
            if (prepared) return;
            prepared = true;
            this.MouseDown += new MouseButtonEventHandler(Area2D_MouseDown);
            PrepareMouseObserver();
            AxisChanged();
            AreaChanged();
            Layers.ItemsAdded += new EventHandler<ItemsEventArgs<ILayer>>(Layers_ItemsAdded);
            Layers.ItemsRemoved += new EventHandler<ItemsEventArgs<ILayer>>(Layers_ItemsRemoved);

            this.Focusable = true;
            this.LostFocus += new RoutedEventHandler(Area2D_LostFocus);
            this.MouseLeave += new MouseEventHandler(Area2D_MouseLeave);

            foreach (var l in Layers)
            {
                l.SetContext(new LayerContext(this), this.View);
            }

            toolTip = this.GetTemplateChild("PART_ToolTip") as Popup;
            contextMenu = this.GetTemplateChild("PART_ContextMenu") as Popup;
            contextMenu.AddHandler(MenuItem.ClickEvent, new RoutedEventHandler(ButtonClicked));
            contextMenu.AddHandler(Button.ClickEvent, new RoutedEventHandler(ButtonClicked));
        }

        private void ButtonClicked(object sender, RoutedEventArgs e)
        {
            CloseContextMenu();
        }

        #region Mouse

        private void PrepareMouseObserver()
        {
            mouseObserverElement = this.GetTemplateChild("PART_MouseObserver") as FrameworkElement;
            mouseObserverElement.SizeChanged += new SizeChangedEventHandler(AreaChanged);

            mouseObserver = new MouseObserver(mouseObserverElement, EnableDoubleClick, MouseButton.Left);
            mouseObserver.Move = MoveByMouse;
            mouseObserver.ClickRight = (b, p, ctrl) => OpenContextMenu(p);
            mouseObserver.MouseWheel = MouseWheel;
            mouseObserver.ToolTipOpen = OpenInfoPopup;
            mouseObserver.ToolTipClose = CloseInfoPopup;
            mouseObserver.ClickLeft = ClickLeft;
            mouseObserver.DoubleClick = DoubleClick;
            mouseObserver.MousePosition = MousePositionChanged;
        }

        private void ClickLeft(MouseButton button, Point p, bool ctrl)
        {
            var args = new MousePosition(p, View.FromRelative(p));
           // if (ctrl) //předávat jenom kliknutí se současným držením CTRL
                foreach (var l in Layers)
                    l.MouseClick(args, ctrl);

            if (Area2DClick != null)
                Area2DClick(this, new AreaClickEventArgs(MouseButton.Left, args, ctrl));
        }

        private void MoveByMouse(Vector vector, Point current, bool ctrl)
        {
            if (!ctrl)
                View.MoveByMouse(vector);
            else
            {
                MouseMoveArgs args = GetMouseMoveArgs(vector, current);
                foreach (var l in Layers)
                    l.MouseMove(args);
            }
        }

        private void MousePositionChanged(Vector vector, Point current, bool ctrlPressed)
        {
            MouseMoveArgs args = GetMouseMoveArgs(vector, current);
            foreach (var l in Layers)
            {
                l.MousePositionChanged(args, ctrlPressed);
            }
        }

        private MouseMoveArgs GetMouseMoveArgs(Vector vector, Point current)
        {
            var previousPosition = new MousePosition(current - vector, View.FromRelative(current - vector));
            var currentPosition = new MousePosition(current, View.FromRelative(current));
            return new MouseMoveArgs(previousPosition, currentPosition);
        }

        private void DoubleClick(MouseButton b, Point p, bool ctrl)
        {
            var args = new MousePosition(p, View.FromRelative(p));
            if (Area2DDoubleClick != null)
                Area2DDoubleClick(this, new AreaClickEventArgs(b, args, ctrl));
        }

        private void MouseWheel(Point position, double stepsCount)
        {
            View.ZoomByMouse(position, (int)stepsCount);
        }

        public void OpenContextMenu(Point p)
        {
            if (contextMenu.IsOpen == false)
            {
                contextMenu.IsOpen = true;
                var panel = contextMenu.FindName("PART_MenuCustomContent") as Panel;
                panel.Children.Clear();
                if (MenuOpening != null) MenuOpening(this, new OpenPopupEventArgs(p, View.FromRelative(p), panel));
            }
        }

        public void CloseContextMenu()
        {
            if (contextMenu.IsOpen == true)
            {
                contextMenu.IsOpen = false;
                if (MenuClosing != null) MenuClosing(this, EventArgs.Empty);
            }
        }

        private void OpenInfoPopup(Point relativePoint, bool ctrlPressed)
        {
            if (toolTip.IsOpen == false)
            {
                toolTip.IsOpen = true;
                var parent = toolTip.FindName("PART_CoordinatesInfo") as FrameworkElement;
                var labelX = toolTip.FindName("PART_LabelX") as FrameworkElement;
                var labelY = toolTip.FindName("PART_LabelY") as FrameworkElement;
                var panel = contextMenu.FindName("PART_InfoCustomContent") as Panel;
                panel.Children.Clear();
                parent.Visibility = ShowCoordinatesInPopup ? Visibility.Visible : Visibility.Collapsed;
                var point = View.FromRelative(relativePoint);
                labelX.DataContext = View.AxisX.GetLabelTextFull(point.X);
                labelY.DataContext = View.AxisY.GetLabelTextFull(point.Y);
                if (InfoPopupOpening != null) InfoPopupOpening(this, new OpenPopupEventArgs(relativePoint, point, panel));
            }
        }

        private void CloseInfoPopup()
        {
            if (toolTip.IsOpen == true)
            {
                toolTip.IsOpen = false;
                if (InfoPopupClosing != null) InfoPopupClosing(this, EventArgs.Empty);
            }
        }

        private void Area2D_MouseLeave(object sender, MouseEventArgs e)
        {
            CloseInfoPopup();
        }

        private void Area2D_LostFocus(object sender, RoutedEventArgs e)
        {
            CloseInfoPopup();
        }

        private void Area2D_MouseDown(object sender, MouseButtonEventArgs e)
        {
            this.Focus();
        }

        #endregion Mouse

        private void Layers_ItemsRemoved(object sender, ItemsEventArgs<ILayer> e)
        {
            foreach (var i in e.Items)
            {
                i.SetContext(null, null);
            }
        }

        private void Layers_ItemsAdded(object sender, ItemsEventArgs<ILayer> e)
        {
            foreach (var i in e.Items)
            {
                i.SetContext(new LayerContext(this), this.View);
            }
        }

        #region Properties

        public AxisDefinition AxisX
        {
            get { return (AxisDefinition)GetValue(AxisXProperty); }
            set { SetValue(AxisXProperty, value); }
        }

        public static readonly DependencyProperty AxisXProperty =
            DependencyProperty.Register("AxisX", typeof(AxisDefinition), typeof(Area2D), new FrameworkPropertyMetadata(new LinearAxis(), AxisChanged));

        public AxisDefinition AxisY
        {
            get { return (AxisDefinition)GetValue(AxisYProperty); }
            set { SetValue(AxisYProperty, value); }
        }

        public static readonly DependencyProperty AxisYProperty =
            DependencyProperty.Register("AxisY", typeof(AxisDefinition), typeof(Area2D), new FrameworkPropertyMetadata(new LinearAxis(), AxisChanged));

        public bool ShowAxisX
        {
            get { return (bool)GetValue(ShowAxisXProperty); }
            set { SetValue(ShowAxisXProperty, value); }
        }

        public static readonly DependencyProperty ShowAxisXProperty =
            DependencyProperty.Register("ShowAxisX", typeof(bool), typeof(Area2D), new UIPropertyMetadata(true));

        public bool ShowAxisY
        {
            get { return (bool)GetValue(ShowAxisYProperty); }
            set { SetValue(ShowAxisYProperty, value); }
        }

        public static readonly DependencyProperty ShowAxisYProperty =
            DependencyProperty.Register("ShowAxisY", typeof(bool), typeof(Area2D), new UIPropertyMetadata(true));

        public ViewInfo View
        {
            get { return (ViewInfo)GetValue(ViewProperty); }
            protected set { SetValue(ViewPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey ViewPropertyKey =
            DependencyProperty.RegisterReadOnly("View", typeof(ViewInfo), typeof(Area2D), new PropertyMetadata());

        public static readonly DependencyProperty ViewProperty = ViewPropertyKey.DependencyProperty;

        public ObservableCollectionEx<ILayer> Layers
        {
            get { return (ObservableCollectionEx<ILayer>)GetValue(LayersProperty); }
            // privátní nebo chráněný setter způsobí nemožnost přidávání položek z XAMLu
        }

        private static readonly DependencyPropertyKey LayersPropertyKey =
            DependencyProperty.RegisterReadOnly("Layers", typeof(ObservableCollectionEx<ILayer>), typeof(Area2D), new PropertyMetadata(new ObservableCollectionEx<ILayer>()));

        public static readonly DependencyProperty LayersProperty = LayersPropertyKey.DependencyProperty;

        public bool ShowCoordinatesInPopup
        {
            get { return (bool)GetValue(ShowCoordinatesInPopupProperty); }
            set { SetValue(ShowCoordinatesInPopupProperty, value); }
        }

        public static readonly DependencyProperty ShowCoordinatesInPopupProperty =
            DependencyProperty.Register("ShowCoordinatesInPopup", typeof(bool), typeof(Area2D), new UIPropertyMetadata(true));

        public bool EnableDoubleClick
        {
            get { return (bool)GetValue(EnableDoubleClickProperty); }
            set { SetValue(EnableDoubleClickProperty, value); }
        }

        public static readonly DependencyProperty EnableDoubleClickProperty =
            DependencyProperty.Register("EnableDoubleClick", typeof(bool), typeof(Area2D), new UIPropertyMetadata(true, EnableDoubleClickChanged));

        #endregion Properties

        private static void EnableDoubleClickChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            var target = sender as Area2D;
            target.mouseObserver.WaitForDoubleClick = target.EnableDoubleClick;
        }

        private static void AxisChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            var target = sender as Area2D;
            target.AxisChanged();
        }

        private void AxisChanged()
        {
            if (this.viewUpdater == null) return;
            this.viewUpdater(ViewInfoBase.AxisXProperty, this.AxisX);
            this.viewUpdater(ViewInfoBase.AxisYProperty, this.AxisY);
        }

        private void AreaChanged(object sender, SizeChangedEventArgs args)
        {
            AreaChanged();
        }

        private void AreaChanged()
        {
            if (this.viewUpdater == null) return;
            viewUpdater(ViewInfoBase.AreaWidthDipProperty, mouseObserverElement.ActualWidth);
            viewUpdater(ViewInfoBase.AreaHeightDipProperty, mouseObserverElement.ActualHeight);
        }

        #region Events

        public event EventHandler<AreaClickEventArgs> Area2DClick;

        public event EventHandler<AreaClickEventArgs> Area2DDoubleClick;

        public event EventHandler MenuClosing;

        public event EventHandler InfoPopupClosing;

        public event EventHandler<OpenPopupEventArgs> MenuOpening;

        public event EventHandler<OpenPopupEventArgs> InfoPopupOpening;

        #endregion Events
    }
}